package mua.core.operation;

import mua.core.interpreter.Global;
import mua.core.operation.basis.*;
import mua.core.operation.function.Export;
import mua.core.operation.function.Return;
import mua.core.operation.judge.If;
import mua.core.operation.judge.Judge;
import mua.core.operation.list.*;
import mua.core.operation.other.ErAll;
import mua.core.operation.other.Load;
import mua.core.operation.other.PoAll;
import mua.core.operation.other.Save;
import mua.core.value.Execution;
import mua.core.value.FunctionBody;
import mua.core.value.MuaValue;

import java.util.ArrayList;
import java.util.Scanner;
import java.util.Stack;

import static mua.core.interpreter.Util.numOfPara;


public class MuaOperation {

    /**
     * 返回包含自带操作或函数操作
     * 如果functionBody不为null，则先判断本地而后判断全局
     * @param s 该字符串
     * @return 返回是否包含
     */
    public static boolean contains(String s, FunctionBody functionBody) {
        boolean flag;
        //局部变量中
        if(functionBody!=null){
            flag = functionBody.getLocalVariable().get(s) instanceof FunctionBody;
            if(!flag) flag = Global.globalVariable.get(s) instanceof FunctionBody;
        }
        //全局变量中
        else{
            flag = Global.globalVariable.get(s) instanceof FunctionBody;
        }
        return Operations.contains(s)||flag;
    }

    /**
     * 尝试进行解析
     * @param operationStack 操作栈
     * @param valueStack 操作数栈
     * @param scanner scanner
     * @param functionBody 如果名字空间为全局，则functionBody为null
     * @return 返回MuaValue 
     */
    public static MuaValue run(Stack<String> operationStack, Stack<MuaValue> valueStack,
                               Scanner scanner, FunctionBody functionBody) {
        if (!operationStack.empty()){
            String opt = operationStack.peek();
            MuaValue a, b, c;
            //判断是否为基本操作
            switch (opt){
                case "make":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        b = valueStack.pop();
                        valueStack.push(Make.run(a,b,functionBody));
                    }
                    break;
                case "thing":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(Thing.run(a, functionBody));
                    }
                    break;
                case "print":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(Print.run(a));
                    }
                    break;
                case "read":
                    operationStack.pop();
                    valueStack.push(Read.run(scanner));
                    break;
                case "add":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        b = valueStack.pop();
                        valueStack.push(Operator.add(a,b));
                    }
                    break;
                case "sub":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        b = valueStack.pop();
                        valueStack.push(Operator.sub(a,b));
                    }
                    break;
                case "mul":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        b = valueStack.pop();
                        valueStack.push(Operator.mul(a,b));
                    }
                    break;
                case "div":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        b = valueStack.pop();
                        valueStack.push(Operator.div(a,b));
                    }
                    break;
                case "mod":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        b = valueStack.pop();
                        valueStack.push(Operator.mod(a,b));
                    }
                    break;
                case "erase":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(Erase.run(a,functionBody));
                    }
                    break;
                case "isname":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(IsName.run(a,functionBody));
                    }
                    break;
                case "run":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(Run.run(a,scanner,functionBody));
                    }
                    break;
                case "eq":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        b = valueStack.pop();
                        valueStack.push(Operator.eq(a,b));
                    }
                    break;
                case "gt":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        b = valueStack.pop();
                        valueStack.push(Operator.gt(a,b));
                    }
                    break;
                case "lt":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        b = valueStack.pop();
                        valueStack.push(Operator.lt(a,b));
                    }
                    break;
                case "and":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        b = valueStack.pop();
                        valueStack.push(Operator.and(a,b));
                    }
                    break;
                case "or":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        b = valueStack.pop();
                        valueStack.push(Operator.or(a,b));
                    }
                    break;
                case "not":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(Operator.not(a));
                    }
                    break;
                case "if":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        b = valueStack.pop();
                        c = valueStack.pop();
                        valueStack.push(If.run(a, b, c, scanner,functionBody));
                    }
                    break;
                case "isnumber":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(Judge.isNumber(a));
                    }
                    break;
                case "isword":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(Judge.isWord(a));
                    }
                    break;
                case "islist":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(Judge.isList(a));
                    }
                    break;
                case "isbool":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(Judge.isBool(a));
                    }
                    break;
                case "isempty":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(Judge.isEmpty(a));
                    }
                    break;
                //注意这里，return和export要直接退出循环直接返回，因为函数到这里就退出了
                case "return":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        return Return.run(a);
                    }
                    break;
                case "export":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(Export.run(a, functionBody));
                    }
                    break;
                case "readlist":
                    operationStack.pop();
                    if(functionBody==null)valueStack.push(ReadList.run(scanner));
                    else valueStack.push(ReadList.run(scanner));
                    break;
                case "word":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        b = valueStack.pop();
                        valueStack.push(Word.run(a,b,functionBody));
                    }
                    break;
                case "sentence":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        b = valueStack.pop();
                        valueStack.push(Sentence.run(a,b));
                    }
                    break;
                case "list":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        b = valueStack.pop();
                        valueStack.push(List.run(a,b));
                    }
                    break;
                case "join":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        b = valueStack.pop();
                        valueStack.push(Join.run(a,b));
                    }
                    break;
                case "first":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(First.run(a));
                    }
                    break;
                case "last":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(Last.run(a));
                    }
                    break;
                case "butfirst":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(ButFirst.run(a));
                    }
                    break;
                case "butlast":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(ButLast.run(a));
                    }
                    break;
                case "random":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(Num.random(a));
                    }
                    break;
                case "int":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(Num.Int(a));
                    }
                    break;
                case "sqrt":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(Num.sqrt(a));
                    }
                    break;
                case "save":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(Save.run(a));
                    }
                    break;
                case "load":
                    if(valueStack.size()-numOfPara(opt, functionBody)>=0){
                        operationStack.pop();
                        a = valueStack.pop();
                        valueStack.push(Load.run(a,scanner,functionBody));
                    }
                    break;
                case "erall":
                    operationStack.pop();
                    valueStack.push(ErAll.run(functionBody));
                    break;
                case "poall":
                    operationStack.pop();
                    valueStack.push(PoAll.run(functionBody));
                    break;
                default:
                    //函数操作
                    //首先判断操作数栈中的操作数是否足够函数的所需参数
                    FunctionBody temp;
                    if(functionBody==null){
                        temp = (FunctionBody)Global.globalVariable.get(opt);
                    } else {
                        temp = (FunctionBody)functionBody.getLocalVariable().get(opt);
                        if(temp==null)temp = (FunctionBody)Global.globalVariable.get(opt);
                    }
                    //如果足够则可以运行
                    if(valueStack.size()>=temp.getArguments().size()){
                        operationStack.pop();
                        //将操作数栈里的数都放到函数内的局部变量中
                        ArrayList<MuaValue> tempArguments = temp.getArguments();
                        Execution execution = new Execution(temp);
                        for(MuaValue muaValue: tempArguments){
                            execution.getLocalVariable().put(muaValue.toString(),valueStack.pop());
                        }
                        MuaValue muaValue = execution.run(scanner);
                        valueStack.push(muaValue);
                    }else return null;
            }
        }
        //如果操作栈不为空则证明操作未完成，返回null
        if(operationStack.isEmpty()){
            if(valueStack.isEmpty())return null;
            else return valueStack.peek();
        }else {
            return null;
        }
    }
}
